package checker

import (
	"mi/compiler/ast"
	"mi/compiler/binder"
	"mi/compiler/diagnostic"
	"mi/internal/xpath"
)


type Program interface {
	GetSourceFile(uri xpath.URI) *ast.SourceFile
	GetPackageTree(path string) *ast.PackageTree
	GetPackage(pro *ast.ProjectFile, tree *ast.PackageTree) *ast.Package
	GetPackageInDefaultLibrary(tree *ast.PackageTree) *ast.Package
	GetPackagePath(pkg *ast.Package) string
	GetPackages() []*ast.Package
	GetSourceFiles() []*ast.SourceFile
}

type TypeChecker interface {
	// GetProgram() Program
	// CheckProgram()
	// EmitFiles() *EmitResult
	GetParentOfSymbol(symbol *binder.Symbol) *binder.Symbol
	GetTypeOfSymbol(symbol *binder.Symbol) Type
	GetTypeOfSymbolAtLocation(symbol *binder.Symbol, location ast.Node) Type
	GetFieldsOfType(tpe Type) []*binder.Symbol
	// getMemberOfType(t *Type, propetyName string) *Symbol
	GetSignaturesOfType(tpe Type, kind SignatureKind) []*Signature
	// getIndexTypeOfType(t *Type, kind indexkind.IndexKind) *Type
	GetReturnTypeOfSignature(signature *Signature) Type
	GetSymbolsInScope(location ast.Node, meaning binder.SymbolFlags) []*binder.Symbol
	GetSymbolInfo(node ast.Node) *binder.Symbol
	GetTypeOfNode(node ast.Node) Type
	GetApparentType(t Type) Type
	TypeToString(tpe Type, enclosingDeclaration ast.Node, useAlias bool) string
	SymbolToString(symbol *binder.Symbol, enclosingDeclaration ast.Node, meaning binder.SymbolFlags, useAlias bool) string
	GetAugmentedPropertiesOfApparentType(t Type) []*binder.Symbol
	GetRootSymbol(symbol *binder.Symbol) *binder.Symbol
	GetContextualType(node ast.Expression) Type
	GetResolvedSignature(node ast.Node) (*Signature, []*Signature)
	GetSignatureFromDeclaration(decl ast.FunctionLike) *Signature
	GetSymbolAtLocation(node ast.Node) *binder.Symbol

	IsUnknownSymbol(symbol *binder.Symbol) bool
	IsUnknownSignature(signature *Signature) bool

	// Should not be called directly.  Should only be accessed through the Program instance.
	// internal 
	GetDiagnostics(sourceFile []*ast.SourceFile) []*diagnostic.Diagnostic
	// internal
	GetSingleFileDiagnostics(sourceFile *ast.SourceFile) []*diagnostic.Diagnostic
	// internal 
	GetGlobalDiagnostics() []*diagnostic.Diagnostic
	// internal 
	GetEmitResolver() EmitResolver

	// internal 
	GetNodeCount() int
	// internal 
	GetIdentifierCount() int
	// internal 
	GetSymbolCount() int
	// internal
	GetTypeCount() int
}

// Interface

func (c *checker) getSortedDiagnostics() []*diagnostic.Diagnostic {
	if !c.produceDiagnostics {
		panic("diagnostics are available only in the full type check mode")
	}

	if c.diagnosticsModified {
		diagnostic.SortDiagnostic(c.diagnostics)
		c.diagnostics = diagnostic.DeduplicateSortedDiagnostics(c.diagnostics)
		c.diagnosticsModified = false
	}
	return c.diagnostics
}

func (c *checker) getDiagnostics(files []*ast.SourceFile) []*diagnostic.Diagnostic {
	for _, file := range files {
		c.checkSourceFile(file)
	}
	return c.getSortedDiagnostics()
}

func (c *checker) getSingleFileDiagnostics(sourceFile *ast.SourceFile) []*diagnostic.Diagnostic {
	c.checkSourceFile(sourceFile)
	return diagnostic.FilterDiagnostic(c.getSortedDiagnostics(), func(d *diagnostic.Diagnostic) bool {
		return d.File == sourceFile
	})
}

func (c *checker) getGlobalDiagnostics() []*diagnostic.Diagnostic {
	return diagnostic.FilterDiagnostic(c.getSortedDiagnostics(), func(d *diagnostic.Diagnostic) bool {
		return d.File == nil
	})
}

func (c *checker) GetNodeCount() int {
	var count = 0
	for _, file := range c.program.GetSourceFiles() {
		count += file.NodeCount
	}
	return count
}

func (c *checker) GetIdentifierCount() int {
	var count = 0
	for _, file := range c.program.GetSourceFiles() {
		count += file.IdentifierCount
	}
	return count
}

func (c *checker) GetSymbolCount() int {
	var count = 0
	for _, file := range c.program.GetSourceFiles() {
		count += file.SymbolCount
	}
	return count
}

func (c *checker) GetTypeCount() int {
	return c.typeCount
}

func (c *checker) GetParentOfSymbol(symbol *binder.Symbol) *binder.Symbol {
	return c.getParentOfSymbol(symbol)
}

func (c *checker) GetTypeOfSymbol(symbol *binder.Symbol) Type {
	return c.getTypeOfSymbol(symbol)
}

func (c *checker) GetTypeOfSymbolAtLocation(symbol *binder.Symbol, location ast.Node) Type {
	return c.getTypeOfSymbolAtLocation(symbol, location)
}

func (c *checker) GetFieldsOfType(tpe Type) []*binder.Symbol {
	return c.getFieldsOfType(tpe)
}

// getMemberOfType(t *Type, propetyName string) *Symbol
func (c *checker) GetSignaturesOfType(tpe Type, kind SignatureKind) []*Signature {
	return c.getSignaturesOfType(tpe, kind)
}

// getIndexTypeOfType(t *Type, kind indexkind.IndexKind) *Type
func (c *checker) GetReturnTypeOfSignature(signature *Signature) Type {
	return c.getReturnTypeOfSignature(signature)
}

func (c *checker) GetSymbolsInScope(location ast.Node, meaning binder.SymbolFlags) []*binder.Symbol {
	return c.getSymbolsInScope(location, meaning)
}

func (c *checker) GetSymbolInfo(node ast.Node) *binder.Symbol {
	return c.getSymbolInfo(node)
}

func (c *checker) GetTypeOfNode(node ast.Node) Type {
	return c.getTypeOfNode(node)
}

func (c *checker) GetApparentType(t Type) Type {
	return c.getApparentType(t)
}

func (c *checker) TypeToString(tpe Type, enclosingDeclaration ast.Node, includeAlias bool) string {
	return c.typeToString(tpe, enclosingDeclaration, includeAlias)
}

func (c *checker) SymbolToString(symbol *binder.Symbol, enclosingDeclaration ast.Node, meaning binder.SymbolFlags, includeAlias bool) string {
	return c.symbolToString(symbol, enclosingDeclaration, meaning, includeAlias)
}

func (c *checker) GetAugmentedPropertiesOfApparentType(tpe Type) []*binder.Symbol {
	return c.getAugmentedPropertiesOfApparentType(tpe)
}

func (c *checker) GetRootSymbol(symbol *binder.Symbol) *binder.Symbol {
	return c.getRootSymbol(symbol)
}

func (c *checker) GetContextualType(node ast.Expression) Type {
	return c.getContextualType(node)
}

func (c *checker) GetResolvedSignature(node ast.Node) (*Signature, []*Signature) {
	return c.getResolvedSignature(node)
}

func (c *checker) GetSignatureFromDeclaration(decl ast.FunctionLike) *Signature{
	return c.getSignatureFromDeclaration(decl)
}

func (c *checker) GetSymbolAtLocation(node ast.Node) *binder.Symbol {
	return c.getSymbolAtLocation(node)
}

func (c *checker) IsUnknownSymbol(symbol *binder.Symbol) bool {
	return c.isUnknownSymbol(symbol)
}

func (c *checker) IsUnknownSignature(signature *Signature) bool {
	return c.isUnknownSignature(signature)
}

// Should not be called directly.  Should only be accessed through the Program instance.
/* iinternal */
func (c *checker) GetDiagnostics(files []*ast.SourceFile) []*diagnostic.Diagnostic {
	return c.getDiagnostics(files)
}


func (c *checker) GetSingleFileDiagnostics(sourceFile *ast.SourceFile) []*diagnostic.Diagnostic {
	return c.getSingleFileDiagnostics(sourceFile)
}

/* internal */
func (c *checker) GetGlobalDiagnostics() []*diagnostic.Diagnostic {
	return c.getGlobalDiagnostics()
}

func (c *checker) GetEmitResolver() EmitResolver {
	return c
}